<?php
/**
 * 配置管理
 *
 * @package if
 * @subpackage core
 * @since 1.0
 */
class IConfig extends IObject {
	private $opts = array();
	private static $modules = array();
	private $updated = false;
	private $module;
	private $file;
	
	/**
	 * 构造
	 *
	 * @param string|array $fileOrOpts 文件名或配置选项
	 * @since 1.0
	 */
	function __construct($fileOrOpts = array()) {
		if (is_array($fileOrOpts)) {
			$this->opts = $fileOrOpts;
		}
		else {
			$this->file = $fileOrOpts;
			if (is_file($fileOrOpts)) {
				if (!if_is_absolute_path($fileOrOpts)) {
					$fileOrOpts = realpath($fileOrOpts);
				}
				$array = require($fileOrOpts);
				if (isset($opt) && is_array($opt)) {
					$this->opts = $opt;
				}
				else if (!is_array($array)) {
					throw new IException("invalid config file '{$fileOrOpts}'", 0x100009);
				}
				else {
					$this->opts = $array;
				}
			}
		}
	}
	
	
	/**
	 * 取得某个模块的配置
	 * 
	 * 查找顺序依次是：
	 * - $framework/config/_module_env_default.php
	 * - $framework/config/_module_default.php
	 * - lib/modules/module/_config_env.php //v1.1.2
	 * - lib/modules/module/_config.php
	 * 
	 * - app/config/_module_env.php
	 * - app/config/_module.php
	 * 
	 * 后两个位置查找出来的结果将和前四个位置查找出来的结果进行合并
	 * 
	 * 如果一个模块定义了另外一个模块的
	 * - module/other_module_env.extend.php //v1.1.2
	 * - module/other_module.extend.php
	 * 则会扩展原有的相应选项
	 * 
	 * 如果一个模块定义了另外一个模块的
	 * - module/other_module_env.override.php //v1.1.2
	 * - module/other_module.override.php
	 * 则会覆盖掉原有的相应选项
	 * 
	 * 可以简单地用 o() 代替此静态方法，比如：
	 * - o("@module.a.b.c");
	 * 其中 module 是模块名，a.b.c相当于：
	 * <code>
	 * array(
	 * 	"a" => array(
	 * 		"b" => array(
	 * 			"c" => "我是a.b.c的值"
	 * 		)
	 * 	)
	 * );
	 * </code>
	 * 
	 * @param string $module 模块名
	 * @return IConfig 
	 * @since 1.0
	 * @see x
	 */
	static function &module($module) {
		$atModule = "@" . $module;
		
		//是否有缓存		
		if (array_key_exists($module, self::$modules)) {
			return self::$modules[$module];
		}
		
		//compile
		global $COMPILED_CONFIG;
		if (!empty($COMPILED_CONFIG) && isset($COMPILED_CONFIG[$atModule])) {
			$config = new IConfig($COMPILED_CONFIG[$atModule]);
			$config->setModule($module);
			self::$modules[$module] = $config;
			return $config;
		}
		
		//默认配置
		$default = null;
		$defaultFile = IF_FW_DIR . "/config/_{$module}_" . IF_APP_ENV . "_default.php";
		if (!is_file($defaultFile)) {
			$defaultFile = IF_FW_DIR . "/config/_{$module}_default.php";
			if (is_file($defaultFile)) {
				$default = new IConfig($defaultFile);
			}
		}
		else {
			$default = new IConfig($defaultFile);
		}
		
		//模块中定义的
		if ($module != "app") {
			$modules = IApplication::singleton()->modules();
			if (isset($modules[$module])) {
				$configFile = $modules[$module] . "/_config_" . IF_APP_ENV . ".php";
				if (is_file($configFile)) {
					$default = new IConfig($configFile);
				}
				else {
					$configFile = $modules[$module] . "/_config.php";
					if (is_file($configFile)) {
						$default = new IConfig($configFile);
					}
				}
			}
		}
		
		//用户自定义的
		$config = null;
		$envFile = IApplication::singleton()->configDir() . "/_{$module}_" . IF_APP_ENV . ".php";
		if (is_file($envFile)) {
			$config = new IConfig($envFile);
		}
		else {
			$file = IApplication::singleton()->configDir() . "/_{$module}.php";
			$config = new IConfig($file);
		}
		$config->setModule($module);
		
		//合并
		if ($default) {
			$opts = array_merge($default->opts(), $config->opts());
			$config->setOpts($opts);
		}
		
		//其他模块对当前模块的继承或覆盖 v1.1.0
		if (IApplication::started()) {
			$opts = $config->opts();
			$modules = IApplication::singleton()->modules();
			foreach ($modules as $dir) {
				//继承
				$extFile = $dir . "/_{$module}_" . IF_APP_ENV . ".extend.php";
				if (is_file($extFile)) {
					$_config = new IConfig($extFile);
					$opts = if_array_merge_recursive($opts, $_config->opts());
				}
				else {
					$extFile = $dir . "/_{$module}.extend.php";
					if (is_file($extFile)) {
						$_config = new IConfig($extFile);
						$opts = if_array_merge_recursive($opts, $_config->opts());
					}
				}
				//覆盖
				$overrideFile = $dir . "/_{$module}_" . IF_APP_ENV . ".override.php";
				if (is_file($overrideFile)) {
					$_config = new IConfig($overrideFile);
					$opts = array_merge($opts, $_config->opts());
				}
				else {
					$overrideFile = $dir . "/_{$module}.override.php";
					if (is_file($overrideFile)) {
						$_config = new IConfig($overrideFile);
						$opts = array_merge($opts, $_config->opts());
					}
				}
			}
			$config->setOpts($opts);
		}
		
		self::$modules[$module] = &$config;
		
		//加入compile
		$COMPILED_CONFIG["IF_MODIFIED"] = true;
		$COMPILED_CONFIG[$atModule] = $config->opts();
		
		return $config;
	}
	
	/**
	 * 取得选项的值
	 * 
	 * 比如： 
	 *  o("@module.opt1.opt2")
	 *
	 * @param string $key
	 * @return mixed
	 * @since 1.0
	 */
	static function opt($key) {
		//缓存中有没有
		if (!empty($GLOBALS["COMPILED_CONFIG"])) {
			$value = self::keysValue($GLOBALS["COMPILED_CONFIG"], $key);
			if (!is_null($value)) {
				return $value;
			}
		}
		
		//全部配置 v1.1.0
		
		if ($key == "@") {
			if (isset($GLOBALS["COMPILED_CONFIG"])) {
				return $GLOBALS["COMPILED_CONFIG"];
			}
			return array();
		}
		
		$pieces = explode(".", $key);
		if (empty($pieces) || !preg_match("/^@/", $pieces[0])) {
			throw new IException("'{$key}' must define a module name (such as @router)", 0x100010);
		}
		$module = substr(array_shift($pieces), 1);
		if (empty($pieces)) {
			return self::module($module)->opts();
		}
		else {
			return self::module($module)->get($pieces);
		}
	}
	
	/**
	 * 取得所有选项
	 *
	 * @return array
	 * @since 1.0
	 */
	function opts() {
		return $this->opts;
	}
	
	/**
	 * 设置选项
	 *
	 * @param array $opts 选项
	 * @since 1.0
	 */
	function setOpts(array $opts) {
		$this->opts = $opts;
		$this->setUpdated(true);
	}
	
	/**
	 * 取得当前配置中某个键的值
	 *
	 * @param array|string $key 键，可以是多级的，比如a.b.c
	 * @return mixed
	 * @since 1.0
	 */
	function get($key) {
		return self::keysValue($this->opts, $key);
	}
	
	/**
	 * 设置当前配置中某个键的值
	 *
	 * @param string $key 键，可以是多级的，比如a.b.c
	 * @param mixed $value 值
	 * @return boolean
	 * @since 1.0
	 */	
	function set($key, $value) {
		$this->opts = if_array_set($this->opts, $key, $value);
		$this->setUpdated(true);
		return true;
	}
	
	/**
	 * 取得一个配置中某个键的值
	 *
	 * @param array $config 配置数组
	 * @param array|string $key 键，可以是多级的，比如a.b.c
	 * @return mixed
	 * @since 1.0
	 * @deprecated 请使用if_array_get代替之
	 */	
	static function keysValue(array $config, $key) {
		return if_array_get($config, $key);
	}
	
	/**
	 * 将配置写入到文件
	 * 
	 * 默认将写在app/config/中
	 *
	 * @param string|IFile|null $file 文件名或文件对象。在v1.1.2中，如果$file为null，则当前对象会试图存到读取当前配置的文件中
	 * @return boolean
	 * @since 1.0
	 */
	function write($file = null) {
		if (is_null($file)) {
			$file = $this->file;
		}
		if (!if_is_instance_of($file, "IFile")) {
			if (!if_is_absolute_path($file)) {
				$file = IApplication::singleton()->directory("config") . "/" . $file;
			}
			$file = new IFile($file);
		}
		$writer = $file->writer();
		$bool = $writer->write("<?php\nif (!defined(\"IF_VERSION\")) {exit();}\nreturn " . var_export($this->opts, true) . ";\n?>");
		$writer->close();
		return $bool;
	}
	
	/**
	 * 判断一个偏移量是否存在
	 *
	 * 实现了 ArrayAccess 接口对应方法
	 * 
	 * @param integer $index
	 * @return boolean
	 * @since 1.0
	 */
	function offsetExists($index) {
		return array_key_exists($index, $this->opts);
	}
	
	/**
	 * 从一个偏移量中取得数据
	 * 
	 * 实现了 ArrayAccess 接口对应方法
	 *
	 * @param integer $index 偏移量
	 * @return mixed
	 * @since 1.0
	 */
	function offsetGet($index) {
		return $this->get($index);
	}
	
	/**
	 * 设置偏移量位置上的值
	 *
	 * 实现了 ArrayAccess 接口对应方法
	 * 
	 * @param integer $index 偏移量
	 * @param mixed $item 值
	 * @since 1.0
	 */
	function offsetSet($index, $item) {
		$this->set($index, $item);
	}
	
	/**
	 * 删除偏移量位置对应的元素
	 * 
	 * 实现了 ArrayAccess 接口对应方法
	 *
	 * @param integer $index 偏移量
	 * @since 1.0
	 */
	function offsetUnset($index) {
		if ($this->offsetExists($index)) {
			unset($this->opts[$index]);
		}
	}	
	
	/**
	 * 配置是否有更新
	 *
	 * @return boolean
	 * @since 1.1.0
	 */
	function isUpdated() {
		return $this->updated;
	}
	
	/**
	 * 设置配置的更新状态
	 *
	 * @param boolean $updated 是否有更新
	 * @since 1.1.0
	 */
	function setUpdated($updated = true) {
		$this->updated = $updated;
	}
	
	/**
	 * 设置当前配置对应的模块名
	 *
	 * @param string $module 模块名
	 * @since 1.1.0
	 */
	function setModule($module) {
		$this->module = $module;
	}
	
	function refresh() {
		if ($this->module) {
			//加入compile
			global $COMPILED_CONFIG;
			$COMPILED_CONFIG["IF_MODIFIED"] = true;
			$COMPILED_CONFIG["@" . $this->module] = $this->opts();
		}
	}
}

?>